﻿using com.nlf.calendar;
using com.nlf.calendar.util;
using PBIW.Operating;
using System.Diagnostics;
using System.Text;
using YiJingFramework.References.Zhouyi;

namespace PBIW.Core
{
    public sealed class Core
    {
        #region Static
        private static bool running = false;
        public static CoreStartingStates Start(CoreStartingArguments arguments, out Core core)
        {
            Debug.Assert(!running);
            running = true;
            core = new Core(arguments, out var result);
            return result;
        }
        #endregion
        public CoreStartingArguments StartingArguments { get; }

        #region Settings
        private readonly Settings settings;

        /// <summary>
        /// 如果启动时的返回不包括 <see cref="CoreStartingProblemsAndErrors.CannotReadOrWriteSettings"/> ，那就不该为 <c>null</c> 。
        /// </summary>
        public string? SettingsFilePath => this.settings.FilePath;

        /// <summary>
        /// 
        /// </summary>
        /// <exception cref="SettingsSavingException"></exception>
        private void SaveSettings()
        {
            try
            {
                this.settings.SaveSettings();
            }
            catch (Exception e)
            {
                throw new SettingsSavingException(e);
            }
        }
        #endregion

        #region Properties
        private PlumBlossomGenerator plumBlossomGenerator;
        public bool GenerateOverlappingByChangedForQianAndKun
        {
            get
            {
                return this.settings.GenerateOverlappingByChangedForQianAndKun;
            }
            set
            {
                this.settings.GenerateOverlappingByChangedForQianAndKun = value;
                this.plumBlossomGenerator = new PlumBlossomGenerator(value);
                this.SaveSettings();
            }
        }

        private ExpressionCalculator? expressionCalculator;
        public bool EnableAutoCalculation
        {
            get
            {
                return this.settings.EnableAutoCalculation;
            }
            set
            {
                this.settings.EnableAutoCalculation = value;
                if (value)
                    this.expressionCalculator = new ExpressionCalculator();
                else
                    this.expressionCalculator = null;
                this.SaveSettings();
            }
        }

        private TextProvider? textProvider;

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        /// <exception cref="YiJingFramework.References.Zhouyi.Exceptions.CannotReadTranslationException"></exception>
        private void ReloadTexts()
        {
            var root = this.StartingArguments.RootPath;
            this.textProvider = new TextProvider(
                Path.Combine(root, "jing.json"),
                Path.Combine(root, "xiang.json"),
                Path.Combine(root, "tuan.json"));
        }
        public void DisableZhouyiReferencing()
        {
            this.settings.EnableZhouyiReferencing = false;
            this.textProvider = null;
            this.SaveSettings();
        }
        public bool TryEnableAndReloadZhouyiReferencing()
        {
            this.settings.EnableZhouyiReferencing = true;
            try
            {
                this.ReloadTexts();
            }
            catch
            {
                return false;
            }
            this.SaveSettings();
            return true;
        }

        private readonly NumberInChineseProvider numberInChineseProvider
            = new NumberInChineseProvider();
        #endregion

        private Core(CoreStartingArguments arguments, out CoreStartingStates state)
        {
            state = CoreStartingStates.FullySucceeded;

            this.StartingArguments = arguments;

            this.settings = Settings.ReadSettings(arguments.ApplicationId, arguments.RootPath);
            if (this.settings.FilePath is null)
                state |= CoreStartingStates.CannotReadOrWriteSettings;

            this.plumBlossomGenerator = new PlumBlossomGenerator(
                this.settings.GenerateOverlappingByChangedForQianAndKun);

            if (this.settings.EnableAutoCalculation)
                this.expressionCalculator = new ExpressionCalculator();

            if (this.settings.EnableZhouyiReferencing)
            {
                try
                {
                    this.ReloadTexts();
                }
                catch
                {
                    this.settings.EnableZhouyiReferencing = false;
                    state |= CoreStartingStates.AutoDisabledZhouyiReferencing;
                }
            }

            try
            {
                this.SaveSettings();
            }
            catch
            {
                state |= CoreStartingStates.CannotReadOrWriteSettings;
            }
        }

        private string? TryToInts(
            string upper, string lower, string changing,
            IReadOnlyDictionary<string, string> timeAndCustomParameters,
            out int upperValue, out int lowerValue, out int changingValue)
        {
            if (this.expressionCalculator is null)
            {
                if (!int.TryParse(upper, out upperValue))
                {
                    upperValue = lowerValue = changingValue = 0;
                    return $"获取上卦失败。" +
                        $"当前您没有开启自动计算功能，" +
                        $"请确认您输入了一个 {int.MinValue} 到 {int.MaxValue} 之间的整数。";
                }
                if (!int.TryParse(lower, out lowerValue))
                {
                    upperValue = lowerValue = changingValue = 0;
                    return $"获取下卦失败。" +
                        $"当前您没有开启自动计算功能，" +
                        $"请确认您输入了一个 {int.MinValue} 到 {int.MaxValue} 之间的整数。";
                }
                if (!int.TryParse(changing, out changingValue))
                {
                    upperValue = lowerValue = changingValue = 0;
                    return $"获取动爻失败。" +
                        $"当前您没有开启自动计算功能，" +
                        $"请确认您输入了一个 {int.MinValue} 到 {int.MaxValue} 之间的整数。";
                }
            }
            else
            {
                var parameters = new Dictionary<string, string>(timeAndCustomParameters);
                if (!this.expressionCalculator.TryCalculate(upper, parameters, out upperValue))
                {
                    upperValue = lowerValue = changingValue = 0;
                    return $"计算上卦失败。" +
                        $"如果屡次发生此问题，且您确定没有输入错误，可尝试关闭自动计算功能。";
                }
                if (!parameters.ContainsKey("上"))
                    parameters.Add("上", upperValue.ToString());
                if (!this.expressionCalculator.TryCalculate(lower, parameters, out lowerValue))
                {
                    upperValue = lowerValue = changingValue = 0;
                    return $"计算下卦失败。" +
                        $"如果屡次发生此问题，且您确定没有输入错误，可尝试关闭自动计算功能。";
                }
                if (!parameters.ContainsKey("下"))
                    parameters.Add("下", lowerValue.ToString());
                if (!this.expressionCalculator.TryCalculate(changing, parameters, out changingValue))
                {
                    upperValue = lowerValue = changingValue = 0;
                    return $"计算动爻失败。" +
                        $"如果屡次发生此问题，且您确定没有输入错误，可尝试关闭自动计算功能。";
                }
            }
            return null;
        }

        private static void AppendLunar(StringBuilder stringBuilder, Lunar lunar, out Lunar changedLunar)
        {
            Solar solar = lunar.getSolar();
            if (solar.getHour() is 23)
            {
                var nextDay = solar.next(1);
                var newSolar = new Solar(
                    nextDay.getYear(),
                    nextDay.getMonth(),
                    nextDay.getDay(),
                    0, 1, 0);
                changedLunar = newSolar.getLunar();
            }
            else
                changedLunar = lunar;

            _ = stringBuilder
                .Append('于')
                .Append(changedLunar.getYearZhi())
                .Append('年')
                .Append(changedLunar.getMonthInChinese())
                .Append('月')
                .Append(changedLunar.getDayInChinese())
                .Append(changedLunar.getTimeZhi())
                .Append("时起得此卦。时 ")
                .Append(solar.getYear())
                .Append(" 年 ")
                .Append(solar.getMonth())
                .Append(" 月 ")
                .Append(solar.getDay())
                .Append(" 日 ")
                .Append(solar.getHour())
                .Append(" 时 ")
                .Append(solar.getMinute())
                .Append(" 分，又纪")
                .Append(changedLunar.getYearInGanZhiExact())
                // .Append('、')
                .Append(changedLunar.getMonthInGanZhiExact())
                // .Append('、')
                .Append(changedLunar.getDayInGanZhiExact())
                // .Append('、')
                .Append(changedLunar.getTimeInGanZhi())
                .Append('。');
        }

        private void AppendHexagrams(
            StringBuilder stringBuilder,
            PlumBlossomHexagrams hexagrams)
        {
            const string yangLine = "------";
            const string yinLine = "--  --";
            if (this.textProvider is not null)
            {
                var lines = this.textProvider.GetLineTexts(hexagrams).ToArray();
                var (upper, lower) = this.textProvider.GetUpperAndLowerNames(hexagrams.Overlapping);

                _ = stringBuilder
                    .Append(this.textProvider.GetName(hexagrams.Original))
                    .Append('之')
                    .Append(this.textProvider.GetName(hexagrams.Changed))
                    .Append('互')
                    .Append(upper == lower ? "重" : upper)
                    .Append(lower)
                    .Append('。');
                if (lines.Length is 1)
                {
                    _ = stringBuilder.AppendLine()
                        .Append("易曰：")
                        .Append(lines[0].jingText)
                        .AppendLine()
                        .Append("象曰：")
                        .Append(lines[0].xiangText)
                        .AppendLine();
                }
                _ = stringBuilder
                    .Append("其卦");
            }
            _ = stringBuilder
                .Append("形如：")
                .AppendLine();
            var zip = hexagrams.Original.Zip(hexagrams.Overlapping, hexagrams.Changed);
            foreach (var (o, op, c) in zip.Reverse())
            {
                _ = stringBuilder
                    .Append("     ")
                    .Append(o.IsYang ? yangLine : yinLine)
                    .Append("     ")
                    .Append(op.IsYang ? yangLine : yinLine)
                    .Append("     ")
                    .Append(c.IsYang ? yangLine : yinLine)
                    .AppendLine();
            }
            return;
        }

        public string? GetAllText(string hexagramName)
        {
            if (this.textProvider is null)
                return null;
            if (!this.textProvider.Zhouyi.TryGetHexagram(hexagramName, out var hexagram))
                return null;

            IEnumerable<ZhouyiHexagram.Line> AsEnumerable(ZhouyiHexagram zhouyiHexagram)
            {
                yield return zhouyiHexagram.FirstLine;
                yield return zhouyiHexagram.SecondLine;
                yield return zhouyiHexagram.ThirdLine;
                yield return zhouyiHexagram.FourthLine;
                yield return zhouyiHexagram.FifthLine;
                yield return zhouyiHexagram.SixthLine;
            }

            var upper = hexagram.UpperTrigram;
            var lower = hexagram.LowerTrigram;

            var xiang = this.textProvider.Xiangzhuan;
            var tuan = this.textProvider.Tuanzhuan;

            StringBuilder result = new StringBuilder();
            if (upper == lower)
            {
                _ = result
                    .Append(hexagram.Name)
                    .Append('为')
                    .Append(upper.Nature);
            }
            else
            {
                _ = result
                    .Append(upper.Nature)
                    .Append(lower.Nature)
                    .Append(hexagram.Name);
            }
            _ = result.AppendLine()
                .Append(hexagram.Text)
                .AppendLine()
                .Append("象曰：")
                .Append(xiang[hexagram])
                .AppendLine()
                .Append("彖曰：")
                .Append(tuan[hexagram])
                .AppendLine()
                .AppendLine();

            var hexagramLines = AsEnumerable(hexagram).Reverse();

            var linePatterns = hexagramLines.Select(
                line => line.YinYang.IsYang ? "     ------     " : "     --  --     ");

            var lineTexts = hexagramLines.Select(line => line.ToString());
            var xiangTexts = hexagramLines.Select(line => xiang[line]);

            var applyNinesOrApplySixes = hexagram.ApplyNinesOrApplySixes;
            if (applyNinesOrApplySixes is not null)
            {
                linePatterns = linePatterns.Prepend("                ");
                lineTexts = lineTexts.Prepend(applyNinesOrApplySixes.ToString().TrimEnd());
                xiangTexts = xiangTexts.Prepend(xiang[applyNinesOrApplySixes]);
            }

            var padding = lineTexts.Select(line => line.Length).Max() + 2;
            lineTexts = lineTexts.Select(text => text.PadRight(padding, '　'));

            foreach (var (pattern, text, xiangText) in linePatterns.Zip(lineTexts, xiangTexts))
            {
                _ = result
                    .Append(pattern)
                    .Append(text)
                    .Append(xiangText)
                    .AppendLine();
            }

            return result.ToString();
        }

        public bool GenerateText(Lunar lunar,
            string upper, string lower, string changing,
            IReadOnlyDictionary<string, string>? customParameters,
            out string resultOrMessage)
        {
            int upperValue, lowerValue, changingValue;
            StringBuilder result = new StringBuilder();

            AppendLunar(result, lunar, out var changedLunar);

            Dictionary<string, string> timeAndCustomParameters =
                customParameters is null ?
                new Dictionary<string, string>() :
                new Dictionary<string, string>(customParameters);

            if (!timeAndCustomParameters.ContainsKey("年"))
                timeAndCustomParameters.Add("年", (changedLunar.getYearZhiIndex() + 1).ToString());
            if (!timeAndCustomParameters.ContainsKey("月"))
                timeAndCustomParameters.Add("月", (changedLunar.getMonth()).ToString());
            if (!timeAndCustomParameters.ContainsKey("日"))
                timeAndCustomParameters.Add("日", (changedLunar.getDay()).ToString());
            if (!timeAndCustomParameters.ContainsKey("时"))
                timeAndCustomParameters.Add("时", (changedLunar.getTimeZhiIndex() + 1).ToString());

            {
                var r = this.TryToInts(
                    upper, lower, changing, timeAndCustomParameters,
                    out upperValue, out lowerValue, out changingValue);
                if (r is not null)
                {
                    resultOrMessage = r;
                    return false;
                }
            }

            var hexagrams =
                this.plumBlossomGenerator.GenerateHexagrams(upperValue, lowerValue, changingValue);

            _ = result.AppendLine()
                .Append("其以 ")
                .Append(upper)
                .Append(" 为")
                .Append(this.numberInChineseProvider.GetString(upperValue))
                .Append("作上卦，以 ")
                .Append(lower)
                .Append(" 为")
                .Append(this.numberInChineseProvider.GetString(lowerValue))
                .Append("作下卦，又以 ")
                .Append(changing)
                .Append(" 为")
                .Append(this.numberInChineseProvider.GetString(changingValue))
                .Append("作动爻，得卦");

            this.AppendHexagrams(result, hexagrams);
            resultOrMessage = result.ToString();
            return true;
        }
    }
}
